#include "DataStream.h"

Chunk::Chunk(const Chunk& tmp)
	:m_id(tmp.m_id),
	m_size(tmp.m_size),
	m_bs(tmp.m_bs)
{
	if(this->m_bs)
	{
		this->m_subChunk=tmp.m_subChunk;
		m_data=NULL;
	}
	else
	{
		m_data=new char[m_size];
		memcpy_s(m_data,m_size,tmp.m_data,m_size);
		m_subChunk.clear();
	}
}

Chunk::Chunk(ChunkID id,size_t size, const char* d)
	:m_id(id),
	m_size(size),
	m_bs(false)
{
	assert(d!=NULL);
	m_data=new char[m_size];
	memcpy_s(m_data,m_size,d,m_size);

}

Chunk::Chunk(ChunkID id,Vector<Chunk*> subChunk)
	:m_id(id),
	m_size(subChunk.size()),
	m_bs(true),
	m_subChunk(subChunk),
	m_data(NULL)
{
}

Chunk::~Chunk()
{
	if(m_bs)
	{
		m_subChunk.clear();
	}
	else
	{
		delete m_data;
	}
	//for_each(m_subChunk.begin(),m_subChunk.end(),delete);
}

Chunk::ChunkID Chunk::GetID() const
{
	return m_id;
}

size_t Chunk::GetSize() const
{
	return m_size;
}

Chunk::ChunkIterator Chunk::GetSubChunkBegin()
{
	assert(m_bs);
	return m_subChunk.begin();
}

Chunk::ChunkIterator Chunk::GetSubChunkEnd()
{
	assert(m_bs);
	return m_subChunk.end();
}

void Chunk::AddSubChunk(Chunk* c)
{
	m_subChunk.push_back(c);
}

bool Chunk::HasSubChunk() const
{
	return m_bs;
}

const char* const Chunk::GetData() const
{
	return m_data;
}

/*
This constructor is used in engine
*/
DataStream::DataStream(string filename)
	:m_fileName(filename),
	m_mainChunk(NULL),
	m_fileHeader("",0)
{
	fstream::openmode mode = fstream::binary | fstream::in;
	m_file.open(filename.c_str(),mode);
	if(!m_file.is_open())
	{
		FILE_EXCEPTION(filename,FAILED_OPEN);
	}
	Load();
}

/*
This constructor is used in XmlConvertToData
*/
DataStream::DataStream(string filename,string _name, VERSION _version,Chunk* c) 
	: m_fileName(filename),
	m_fileHeader(_name,_version),
	m_mainChunk(c)
{
	fstream::openmode mode = fstream::binary | fstream::out | fstream::trunc;

	m_file.open(filename.c_str(),mode);
	if(!m_file.is_open())
	{
		FILE_EXCEPTION(filename,FAILED_OPEN);
	}
}

DataStream::~DataStream()
{
	m_file.close();
}

void DataStream::SeekToBegin()
{
	m_file.seekg(0, ios::beg);
}

void DataStream::SeekToEnd()
{
	m_file.seekg(0,ios::end);
}

void DataStream::SeekFromBegin(size_t offset)
{
	m_file.seekg(offset,ios::beg);
}

void DataStream::SeekFromCur(size_t offset)
{
	m_file.seekg(offset,ios::cur);
}

size_t DataStream::GetFileLength()
{

	streamoff offset=m_file.tellg();
	m_file.seekg (0, ios::end);
	streamoff length = m_file.tellg();
	m_file.seekg (offset, ios::beg);
	return length;
}

string DataStream::GetChunkName() const
{
	return m_fileHeader.name;
}

Chunk* DataStream::GetMainChunk() const
{
	return m_mainChunk;
}

void DataStream::WriteBuffer(void* buffer,size_t size )
{
	try
	{
		m_file.write(static_cast<char*>(buffer),size);
	}
	catch(ios_base::failure& f)
	{
		FILE_EXCEPTION(m_fileName.c_str(),FAILED_WRITE)
	}
}

void DataStream::ReadBuffer(void* data, size_t length)
{
	try
	{
		m_file.read(static_cast<char*>(data),length);
	}
	catch(ios_base::failure& f)
	{
		FILE_EXCEPTION(m_fileName.c_str(),FAILED_READ)
	}
}

bool DataStream::Save()
{
	cout<<"Save"<<endl;
	if(!m_file.is_open())
	{
		return false;
	}

	SeekToBegin();
	m_file.write(m_fileHeader.name.c_str(),m_fileHeader.name.length()+1);
	WriteBuffer(&m_fileHeader.version,sizeof(m_fileHeader.version));

	//Write chunk
	if(m_mainChunk==NULL)
		EXCEPTION("")
		m_file<<m_mainChunk;
	return true;
}

bool DataStream::Load()
{
	cout<<"Load"<<endl;
	if(!m_file.is_open())
	{
		return false;
	}
	SeekToBegin();
	//Write header
	string name;
	char c;
	m_file>>c;
	while(c!='\0')
	{
		name.push_back(c);
		m_file>>c;
	}
	ReadBuffer(&m_fileHeader.version,sizeof(VERSION));
	m_fileHeader.name=name;

	m_file>>&m_mainChunk;
	return true;
}


fstream& operator<<(fstream& f,Chunk* c)
{
	Chunk::ChunkID id=c->GetID();
	size_t size=c->GetSize();
	bool b=c->HasSubChunk();

	f.write(id.c_str(),id.length()+1);
	f.write(reinterpret_cast<char*>(&size),4);
	f.write(reinterpret_cast<char*>(&b),1);
	if(b)
	{

		for(Chunk::ChunkIterator iter=c->GetSubChunkBegin();iter!=c->GetSubChunkEnd();iter++)
		{
			f<<*iter;
		}
	}
	else
	{
		const char* const data=c->GetData();
		f.write(data,size);
	}
	return f;
}

fstream& operator>>(fstream& f, Chunk** c)
{
	size_t size=0;
	bool b=false;
	string name;
	char ch;
	f>>ch;
	while(ch!='\0')
	{
		name.push_back(ch);
		f>>ch;
	}
	f.read(reinterpret_cast<char*>(&size),4);
	f.read(reinterpret_cast<char*>(&b),1);
	if(b)
	{
		Vector<Chunk*> subChunk;
		for(int i=0;i<size;i++)
		{
			Chunk* tmp;
			f>>(&tmp);
			subChunk.push_back(tmp);

		}
		*c=new Chunk(name,subChunk);
	}
	else
	{
		char* data=new char[size];
		f.read(data,size);
		*c=new Chunk(name,size,data);
	}
	return f;
}
